package main

import (
	"database/sql"
	"embed"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync/atomic"
	"syscall"
	"time"

	"gopkg.in/mgo.v2"

	"github.com/ahmetb/go-linq"
	"github.com/energye/systray"
	_ "github.com/go-sql-driver/mysql"
	"github.com/minio/minio-go/v6"
	"github.com/shirou/gopsutil/process"
)

var stop_flag atomic.Bool
var session *mgo.Session
var mysqldb *sql.DB
var minio_session *minio.Client

//go:embed logo.ico
var embedFs embed.FS

func main() {
	kernel32 := syscall.NewLazyDLL("kernel32.dll")
	user32 := syscall.NewLazyDLL("user32.dll")
	consoleHandle, _, _ := kernel32.NewProc("GetConsoleWindow").Call()
	go func() {
		for len(os.Args) > 1 {
			pids, err := process.Processes()
			if err == nil && linq.From(pids).All(func(p interface{}) bool {
				i, _ := strconv.Atoi(os.Args[1])
				if p.(*process.Process).Pid == (int32)(i) {
					return false
				} else {
					return true
				}
			}) {
				fmt.Print("Exit Process")
				stop_flag.Store(true)
				systray.Quit()
				//os.Exit(0)
			}
		}
	}()
	systray.Run(func() {
		//config
		var err error
		stop_flag.Store(false)
		json_data, _ := os.ReadFile("DataSynConfig.json")
		json_obj := make(map[string]interface{}, 0)
		if err := json.Unmarshal(json_data, &json_obj); err != nil {
			log.Fatal(err)
		} else {
			log.Println("Setting Success.")
		}
		config := json_obj["config"].(map[string]interface{})
		mysqldb, err = sql.Open("mysql", config["mysql"].(string))
		if err != nil {
			log.Println("mysql:", err)
		} else {
			log.Println("mysql connect Success.")
		}
		//mongdb
		if config["mongodb"] != nil {
			session, err = mgo.Dial(config["mongodb"].(string))
			if err != nil {
				log.Println("mongodb:", err)
			} else {
				log.Println("mongodb connect Success.")
			}
		}
		//minio
		if config["minio"] != nil {
			minio_session, err = minio.New(config["minio"].(map[string]interface{})["endpoint"].(string),
				config["minio"].(map[string]interface{})["accessKeyID"].(string),
				config["minio"].(map[string]interface{})["secretAccessKey"].(string),
				config["minio"].(map[string]interface{})["useSSL"].(bool))
			if err != nil {
				log.Println("minio:", err)
			} else {
				log.Println("minio connect Success.")
			}
		}

		stop_flag.Store(false)
		ico, _ := embedFs.ReadFile("logo.ico")
		systray.SetTitle("File Sync")
		systray.SetIcon(ico)
		systray.SetTooltip("File Sync")
		start := systray.AddMenuItem("Start", "Start Sync")
		start.Disable()
		stop := systray.AddMenuItem("Stop", "Stop Sync")
		systray.AddSeparator()
		show := systray.AddMenuItem("Show", "Stop Sync")
		hide := systray.AddMenuItem("Hide", "Stop Sync")
		systray.AddSeparator()
		quit := systray.AddMenuItem("Quit", "Quit the application")

		systray.SetOnDClick(func(menu systray.IMenu) {
			go Run()
			stop.Enable()
			start.Disable()
		})
		systray.SetOnRClick(func(menu systray.IMenu) {
			menu.ShowMenu()
		})
		start.Click(func() {
			go Run()
			stop.Enable()
			start.Disable()
		})
		stop.Click(func() {
			stop_flag.Store(true)
			start.Enable()
			stop.Disable()
		})
		show.Click(func() {
			user32.NewProc("ShowWindowAsync").Call(consoleHandle, 5)
			user32.NewProc("BringWindowToTop").Call(consoleHandle)
		})
		hide.Click(func() {
			user32.NewProc("ShowWindowAsync").Call(consoleHandle, 0)
		})
		quit.Click(func() {
			stop_flag.Store(true)
			mysqldb.Close()
			session.Close()
			systray.Quit()
		})

		go Run()
	}, func() {
		now := time.Now()
		log.Println("Exit at", now.String())
	})
}

func Run() {
	for !stop_flag.Load() {
		rows, err := mysqldb.Query("SELECT d.id,f.id,trail,ip_address,is_folder FROM data_config d,fleet f where f.id=d.instance_id;")
		if err == nil {
			for rows.Next() {
				var (
					config_id  int64
					fleet_id   int64
					trail      string
					ip_address string
					dirpath    string
					gridfs     *mgo.GridFS
					bucket     string
				)
				data_base := "qar"
				err = rows.Scan(&config_id, &fleet_id, &trail, &ip_address, &dirpath)
				if err != nil {
					log.Fatal("mysql read error")
				}
				if session != nil {
					names, err := session.DatabaseNames()
					if err != nil {
						log.Println(err, names)
					}
					if linq.From(names).Where(func(i interface{}) bool {
						return i.(string) == data_base
					}).Count() == 0 {
						log.Fatal("mongodb database not exist")
					}
					bucket = trail + "-QAR"
					gridfs = session.DB(data_base).GridFS(bucket)
				}
				if minio_session != nil {
					bucket = strings.ToLower(trail + "-QAR")
					if exists, err := minio_session.BucketExists(bucket); err != nil || !exists {
						minio_session.MakeBucket(bucket, data_base)
					}
				}
				if len(bucket) == 0 {
					log.Fatal("bucket is empty")
				}
				fullpath := "\\\\" + ip_address + "\\" + dirpath
				filepath.Walk(strings.Replace(fullpath, ":", "$", 1), func(path string, info os.FileInfo, err error) error {
					if err != nil || info.IsDir() {
						return err
					}

					start_time := time.Now()
					fileinfo, _ := os.Stat(path)
					filename := path[len(fullpath)+1:]
					if gridfs != nil {
						file, err := gridfs.Open(filename)
						go func() {
							if err != nil || fileinfo.Size() != file.Size() || fileinfo.ModTime().Unix() > file.UploadDate().Unix() {
								log.Print("sync file:", path)
								if err == nil {
									gridfs.Remove(filename)
								}
								data, err := os.Open(path)
								if err != nil {
									log.Print(err)
									return
								}
								file, err = gridfs.Create(filename)
								file.SetUploadDate(fileinfo.ModTime())
								if err != nil {
									log.Print(err)
									return
								}
								buf := make([]byte, 1024)
								for {
									n, err := data.Read(buf)
									if err != nil || err == io.EOF {
										if err != io.EOF {
											log.Print(err)
										}
										break
									}
									file.Write(buf[0:n])
								}
								defer file.Close()
								defer data.Close()
								WriteDB(bucket, filename, config_id, fleet_id, start_time)
							}
						}()
					}
					if minio_session != nil {
						minio_file, _ := minio_session.StatObject(bucket, filename, minio.StatObjectOptions{})
						go func() {
							if len(minio_file.Key) == 0 || fileinfo.Size() != minio_file.Size || fileinfo.ModTime().Unix() > minio_file.LastModified.Unix() {
								file, err := os.Open(path)
								if err != nil {
									log.Printf("open : %s fail: %s", path, err)
									return
								}
								if len(minio_file.Key) != 0 {
									minio_session.RemoveObject(bucket, filename)
								}
								log.Print("sync file:", path)
								_, err = minio_session.PutObject(bucket, filename, file, -1, minio.PutObjectOptions{})
								if err != nil {
									log.Println("putObject fail: ", err)
									return
								}
								defer file.Close()
								WriteDB(bucket, filename, config_id, fleet_id, start_time)
							}
						}()
					}
					return nil
				})
			}
		}
	}
}
func WriteDB(bucket, filename string, config_id int64, fleet_id int64, start_time time.Time) {
	var file_id int64
	err := mysqldb.QueryRow("select id from data_file where name=? and path=?", bucket, filename).Scan(&file_id)
	if err != nil {
		mysqldb.Exec("insert into data_file(data_config_id,name,instance_id,path,starttime,endtime,state,type) values(?,?,?,?,?,?,?,?)", config_id, bucket, fleet_id, filename, start_time, time.Now(), 0, 0)
	} else {
		mysqldb.Exec("update data_file set data_config_id=?,name=?,instance_id=?,path=?,starttime=?,endtime=? where id=?", config_id, bucket, fleet_id, filename, start_time, time.Now(), file_id)
	}
}
